/*
 *
 *  Copyright (C) 2010 Tal Eisenberg
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fork;

import fork.modbox.ModboxGUI;
import fork.modbox.NodeCube;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.UIManager;
import tal.drivechain.chain.AbortChainException;
import tal.drivechain.chain.Chain;
import tal.drivechain.chain.ChainListener;
import tal.drivechain.chain.ChainNode;
import tal.drivechain.chain.FreePatcher;
import tal.drivechain.chain.NodeListener;
import tal.drivechain.chain.script.ScriptCommand;
import tal.drivechain.chain.script.ScriptCompiler;
import tal.drivechain.gui.GUIModule;
import tal.drivechain.plugin.Channel;
import tal.drivechain.plugin.Module;
import tal.drivechain.system.Globals;
import tal.drivechain.variable.types.ArrayType;
import tal.drivechain.variable.types.DataType;
import tal.drivechain.variable.SetProperty.Flag;
import tal.drivechain.variable.Variable;

/**
 *
 * @author eisental
 */
public class Modbox extends GUIModule implements ChainListener, NodeListener {
    private ModboxGUI gui;
    private Chain chain;
    private Variable varSelectedMods, varActiveChain, varShowScript, varShellCommand;

    @Override
    public void setupProperties() throws IllegalArgumentException, AbortChainException {
        UIManager.put("ScrollBar.width", new Integer(8));
        varSelectedMods = new Variable("selectedMods", new ArrayType(DataType.STRING), new ArrayList<String>(), true);
        varActiveChain = new Variable("activeChain", DataType.STRING, null, true);
        varShowScript = new Variable("dump", DataType.STRING, null, true);
        varShellCommand = new Variable("shell", DataType.STRING, null, true);

        gui = new ModboxGUI(this, (Font)props.getProperty("font"));
        component = gui;
        super.setupProperties();

        gui.init(color, 640, 480);
        setWidth((Integer)props.getProperty("width"), null);
        setHeight((Integer)props.getProperty("height"), null);
        setBoxChain((String)props.getProperty("boxChain"), null);
    }

    public void setBoxChain(String newChain, Flag flag) {

        if (newChain==null) this.setActualBoxChain(null);
        else {
            Chain c = Globals.findChain(newChain, null);
            this.setActualBoxChain(c);
        }
    } public String getBoxChain() { return chain.getPath(); }

    private boolean firstBoxChain = true;
    public void setActualBoxChain(Chain newChain) {

        if (newChain==null) { newChain = Globals.getRootChain();}

        if (newChain==this.chain) return;
        else if (chain!=null) {
            chain.removeChainListener(this);
            chain.removeNodeListener(this);
        }

        this.chain = newChain;
        this.chain.addChainListener(this);
        this.chain.addNodeListener(this);
        varActiveChain.setValue(this.chain.getPath(), true);
        if (isRunning() && !firstBoxChain) {
            sendToOutputs(varActiveChain, -1);
        }
        if (gui!=null && gui.getGraph()!=null && gui.getChainPathPanel()!=null) {
            gui.getChainPathPanel().setChain(this.chain);
            gui.getChildrenPanel().setChain(this.chain);
            gui.getVariablePopup().setChain(this.chain);
            gui.getGraph().clear();
            gui.getScroll().getViewport().setViewPosition(new Point(0,0));

            String locs = this.chain.getMetadata(".modbox");
            
            for (ChainNode node : this.chain.getChildNodes().values())
                gui.addNode(node);

            if (locs!=null && locs.trim().length()>0)
                gui.getGraph().readModLocations(locs);
            gui.validate();
        }

        firstBoxChain = false;
    }
    public Chain getActualBoxChain() { return chain; }

    public void setWidth(Integer value, Flag flag) {
        Dimension d = new Dimension(value, gui.getHeight());
        component.setSize(d);
        component.setPreferredSize(d);
        component.revalidate();
    } public Integer getWidth() { return gui.getWidth(); }

    public void setHeight(Integer value, Flag flag) {
        Dimension d = new Dimension(gui.getWidth(), value);
        component.setSize(d);
        component.setPreferredSize(d);
        component.revalidate();
    } public Integer getHeight() { return gui.getHeight(); }

    @Override
    public void setFont(Font value) {
        super.setFont(value);
        gui.setFont(font);
    }

    @Override
    public void setColor(Color value) {
        super.setColor(value);
        gui.setColor(color);
    }

    @Override
    protected void msgReceived(Variable in, long timestamp, Channel channel) {
    }

    public void chainStopped(Chain src) {
        gui.getChainPathPanel().setChainState(false);
    }

    public boolean chainStopping(Chain src) {
        return true;
    }

    public boolean chainStarting(Chain src) {
        gui.getChainPathPanel().setChainState(true);
        return true;
    }

    public ModboxGUI getGUI() {
        return gui;
    }

    public void sendSelectionChange() {
        List<String> selection = new ArrayList<String>();
        for (NodeCube c : gui.getGraph().getSelectedCubes())
            selection.add(c.getChainNode().getName());
        varSelectedMods.setValue(selection, true);
        if (isRunning()) sendToOutputs(varSelectedMods, -1);
    }

    public void nodeConfigured(Chain src, ChainNode node) {
    }

    public void childRemoved(Chain src, ChainNode m) {
        gui.getGraph().removeNodeCube(m);
    }

    public void childAdded(Chain src, ChainNode m) {
        gui.addNode(m);
    }

    public void messageReceived(ChainNode src, Variable var, Channel channel, long timestamp) {
    }

    public void messageSent(ChainNode src, Variable var, long timestamp) {
    }

    public void nameChanged(ChainNode src) {
        this.getGUI().getChainPathPanel().setChain(chain);
    }

    public void metaChanged(ChainNode src) {
    }

    public void connectionsChanged(ChainNode src) {
    }

    public void nodeDestroyed(ChainNode src) {
        //this.setActualBoxChain(chain.getParentChain());
    }

    public void nodeMoved(ChainNode src) {
    }

    public void propertyChanged(ChainNode src, String name, Object value) {

    }

    public void updateChainMeta() {
        Chain c = this.chain;
        String meta = gui.getGraph().makeLocationString();
        c.putMetadata(".modbox", meta);
        if (getDebugFlag()) {
            log("Updating meta data: " + meta, MSG.DEBUG);
        }
    }

    public void createNewNode(String name, String modClass, Point location) {
        ChainNode n = null;

        if (name!=null && name.trim().length()==0) {
            name = null;
        }

        if (modClass.equals("Chain")) {
            Chain newChain = new Chain(name);
            this.getActualBoxChain().addChild(newChain);
            newChain.setPatcher(new FreePatcher());
            newChain.addDefaultMeta();
            n = newChain;
        } else {
            ScriptCommand moduleCommand = new ScriptCommand("module", name, modClass);
            try {
                Module module = ScriptCompiler.parseModuleCommand(getActualBoxChain(), moduleCommand);
                module.setupProperties();
                module.getParentChain().dispatchNodeConfigured(module);
                if (this.getActualBoxChain().isRunning()) module.start();

                n = module;
            } catch (IllegalArgumentException ex) {
                log("While creating new node: " + ex, MSG.ERROR);
            } catch (AbortChainException ex) {
                log("While creating new node: " + ex, MSG.ERROR);
            }
        }

        Point graphLoc = getGUI().getGraph().getLocationOnScreen();
        int x = location.x - graphLoc.x;
        int y = location.y - graphLoc.y;
        
        if (n!=null) {
            for (NodeCube c : this.getGUI().getGraph().getCubes()) {
                if (c.getChainNode()==n) {
                    c.setLocation(x, y);
                    c.setSelected(true);
                } else c.setSelected(false);
            }
            this.updateChainMeta();
            this.sendSelectionChange();
        }

    }

    public void childRenamed(Chain src, ChainNode child) {
        gui.getChildrenPanel().refresh();
    }

    public void loadChain(String name, String path, Point location) throws IllegalArgumentException {
        System.out.println("mm");
        String chainInFile = null;
        if (name==null || name.trim().length()==0) name = null;

        int atIdx = path.indexOf("@");
        if (atIdx==-1) chainInFile = null;
        else {
            chainInFile = path.substring(atIdx+1).trim();
            path = path.substring(0, atIdx).trim();
        }

        ScriptCompiler s = new ScriptCompiler();
        File f = ScriptCompiler.getScriptFile(path, this.getActualBoxChain().getScriptFilePath(), false);
        log("Loading chains from " + f.getPath() + "...", MSG.ANOUNCE);
        s.loadFromFile(f);
        s.parseScript();
        Chain[] chains = null;
        try {
            chains = s.compileScript(null);

            if (chainInFile==null && name!=null && chains.length>1)
                throw new IllegalArgumentException("Script file " + path + " has more than one chain. Can't load multiple chains with the same name.");
            else if (chainInFile==null && name==null && chains.length>1) {
                // means a batch load of a couple of chains.
                for (Chain c : chains) {
                    c.isLoadedFromFile = true;
                    this.getActualBoxChain().addChild(c);
                    log("added chain " + c.getPath() + ".", MSG.DEBUG);
                }
            } else if (chainInFile!=null) {
                Chain retChain = null;
                for (Chain c : chains) {
                    if (c.getName().equals(chainInFile)) retChain = c;
                    else c.destroy();
                }
                if (retChain==null) throw new IllegalArgumentException("Can't find chain " + chainInFile + " in script file " + path);
                else {
                    if (name!=null)
                        retChain.setName(name);
                    retChain.isLoadedFromFile = true;
                    this.getActualBoxChain().addChild(retChain);
                    log("added chain " + retChain.getPath() + ".", MSG.DEBUG);
                }
            } else if (chains.length==1) {
                Chain c = chains[0];
                if (name!=null) c.setName(name);
                c.isLoadedFromFile = true;
                this.getActualBoxChain().addChild(c);
                log("added chain " + c.getPath() + ".", MSG.DEBUG);
            }
        } catch (AbortChainException ex) {
            if (chains!=null)
                for (Chain c : chains) { c.destroy(); }
            ex.printStackTrace();
            throw new IllegalArgumentException(ex);
        } catch (IllegalArgumentException ex) {
            if (chains!=null)
                for (Chain c : chains) { c.destroy(); }
            ex.printStackTrace();
            throw ex;
        }

        if (chains==null || chains.length==0 || chains.length>1) return;

        Point graphLoc = getGUI().getGraph().getLocationOnScreen();
        int x = location.x - graphLoc.x;
        int y = location.y - graphLoc.y;

        if (chains[0]!=null) {
            for (NodeCube c : this.getGUI().getGraph().getCubes()) {
                if (c.getChainNode()==chains[0]) {
                    c.setLocation(x, y);
                    c.setSelected(true);
                } else c.setSelected(false);
            }
            this.updateChainMeta();
            this.sendSelectionChange();
        }

    }

    public void sendShowScript() {
        if (this.getActualBoxChain()!=null) {
            varShowScript.setValue(varActiveChain);
            sendToOutputs(varShowScript, -1);
        }

    }

    public void sendShellCommand(String string) {
        varShellCommand.setValue(string, true);
        sendToOutputs(varShellCommand, -1);
    }

}
